package com.im.tcp.utils;

import com.alibaba.fastjson.JSONObject;
import com.im.codec.pack.user.UserStatusChangeNotifyPack;
import com.im.codec.proto.MessageHeader;
import com.im.common.constant.Constants;
import com.im.common.enums.ImConnectStatusEnum;
import com.im.common.enums.command.UserEventCommand;
import com.im.common.model.UserClientDto;
import com.im.common.model.UserSession;
import com.im.tcp.publish.MqMessageProducer;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.AttributeKey;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 用于管理用户连接的工具类
 */
public class SessionSocketHolder {

    /**
     * 存储用户连接信息的 ConcurrentHashMap
     * key：用户连接信息的 Dto
     * value：连接的 NioSocketChannel 对象
     */
    private static final Map<UserClientDto, NioSocketChannel> CHANNELS = new ConcurrentHashMap<>();

    /**
     * 将用户连接信息与对应的 NioSocketChannel 对象放入 CHANNELS 中
     *
     * @param appId      应用ID
     * @param userId     用户ID
     * @param clientType 客户端类型
     * @param imei       设备imei号
     * @param channel    NioSocketChannel对象
     */
    public static void put(Integer appId, String userId, Integer clientType, String imei, NioSocketChannel channel) {
        UserClientDto dto = new UserClientDto();
        dto.setUserId(userId);
        dto.setClientType(clientType);
        dto.setAppId(appId);
        dto.setImei(imei);
        CHANNELS.put(dto, channel);
    }

    /**
     * 根据应用ID、用户ID、客户端类型、设备imei号获取对应的 NioSocketChannel 对象
     *
     * @param appId      应用 ID
     * @param userId     用户 ID
     * @param clientType 客户端类型
     * @return 对应的 NioSocketChannel 对象，如果不存在则返回 null
     */
    public static NioSocketChannel get(Integer appId, String userId, Integer clientType, String imei) {
        UserClientDto dto = new UserClientDto();
        dto.setUserId(userId);
        dto.setClientType(clientType);
        dto.setAppId(appId);
        dto.setImei(imei);
        return CHANNELS.get(dto);
    }

    /**
     * 根据应用ID、用户ID获取对应的NioSocketChannel 对象
     *
     * @param appId  应用ID
     * @param userId 用户ID
     * @return
     */
    public static List<NioSocketChannel> get(Integer appId, String userId) {
        Set<UserClientDto> channelInfos = CHANNELS.keySet();
        List<NioSocketChannel> channels = new ArrayList<>();
        channelInfos.forEach(channel -> {
            if (channel.getAppId().equals(appId) && userId.equals(channel.getUserId())) {
                channels.add(CHANNELS.get(channel));
            }
        });
        return channels;
    }

    /**
     * 根据应用 ID、用户 ID、客户端类型移除对应的 NioSocketChannel 对象
     *
     * @param appId      应用 ID
     * @param userId     用户 ID
     * @param clientType 客户端类型
     */
    public static void remove(Integer appId, String userId, Integer clientType, String imei) {
        UserClientDto dto = new UserClientDto();
        dto.setUserId(userId);
        dto.setClientType(clientType);
        dto.setAppId(appId);
        dto.setImei(imei);
        CHANNELS.remove(dto);
    }

    /**
     * 根据 NioSocketChannel 对象移除对应的用户连接信息
     *
     * @param channel NioSocketChannel 对象
     */
    public static void remove(NioSocketChannel channel) {
        // 遍历 CHANNELS 中的所有元素，如果对应的 value 等于参数 channel，就移除该元素
        CHANNELS.entrySet().stream().filter(entity -> entity.getValue() == channel).forEach(entry -> CHANNELS.remove(entry.getKey()));
    }

    /**
     * 从系统中移除用户会话，包括从SessionSocketHolder、Redis中移除会话并发送用户状态更改通知。
     *
     * @param nioSocketChannel 与用户会话关联的 NioSocketChannel。
     */
    public static void removeUserSession(NioSocketChannel nioSocketChannel) {
        // 从通道的属性中获取用户信息
        String userId = (String) nioSocketChannel.attr(AttributeKey.valueOf(Constants.UserId)).get();
        Integer appId = (Integer) nioSocketChannel.attr(AttributeKey.valueOf(Constants.AppId)).get();
        Integer clientType = (Integer) nioSocketChannel.attr(AttributeKey.valueOf(Constants.ClientType)).get();
        String imei = (String) nioSocketChannel.attr(AttributeKey.valueOf(Constants.Imei)).get();

        // 从SessionSocketHolder中移除会话
        SessionSocketHolder.remove(appId, userId, clientType, imei);

        // 从Redis中移除会话
        RedissonClient redissonClient = (RedissonClient) ApplicationContextHelper.getBean(RedissonClient.class);
        RMap<Object, Object> map = redissonClient.getMap(appId + Constants.RedisConstants.UserSessionConstants + userId);
        map.remove(clientType + ":" + imei);

        // 准备用户状态更改通知的消息头
        MessageHeader messageHeader = new MessageHeader();
        messageHeader.setAppId(appId);
        messageHeader.setImei(imei);
        messageHeader.setClientType(clientType);

        // 准备用户状态更改通知
        UserStatusChangeNotifyPack pack = new UserStatusChangeNotifyPack();
        pack.setAppId(appId);
        pack.setUserId(userId);
        pack.setStatus(ImConnectStatusEnum.OFFLINE_STATUS.getCode());

        // 发送用户状态更改通知
        MqMessageProducer mqMessageProducer = (MqMessageProducer) ApplicationContextHelper.getBean(MqMessageProducer.class);
        mqMessageProducer.sendMessage(pack, messageHeader, UserEventCommand.USER_ONLINE_STATUS_CHANGE.getCommand());

        // 关闭通道
        nioSocketChannel.close();
    }

    /**
     * 离线处理用户会话，包括从 SessionSocketHolder 中移除会话、更新用户状态并发送消息通知等。
     *
     * @param nioSocketChannel 通道对象
     */
    public static void offlineUserSession(NioSocketChannel nioSocketChannel) {
        String userId = (String) nioSocketChannel.attr(AttributeKey.valueOf(Constants.UserId)).get();
        Integer appId = (Integer) nioSocketChannel.attr(AttributeKey.valueOf(Constants.AppId)).get();
        Integer clientType = (Integer) nioSocketChannel.attr(AttributeKey.valueOf(Constants.ClientType)).get();
        String imei = (String) nioSocketChannel.attr(AttributeKey.valueOf(Constants.Imei)).get();

        // 从SessionSocketHolder中移除用户会话
        SessionSocketHolder.remove(appId, userId, clientType, imei);

        RedissonClient redissonClient = (RedissonClient) ApplicationContextHelper.getBean(RedissonClient.class);
        // 从Redis中获取用户会话信息
        RMap<String, String> map = redissonClient.getMap(appId + Constants.RedisConstants.UserSessionConstants + userId);
        String sessionStr = map.get(clientType + ":" + imei);

        // 更新用户状态为离线
        if (!StringUtils.isBlank(sessionStr)) {
            UserSession userSession = JSONObject.parseObject(sessionStr, UserSession.class);
            userSession.setConnectState(ImConnectStatusEnum.OFFLINE_STATUS.getCode());
            map.put(clientType + ":" + imei, JSONObject.toJSONString(userSession));
        }

        // 构建消息头和用户状态变更消息体
        MessageHeader messageHeader = new MessageHeader();
        messageHeader.setAppId(appId);
        messageHeader.setImei(imei);
        messageHeader.setClientType(clientType);

        UserStatusChangeNotifyPack pack = new UserStatusChangeNotifyPack();
        pack.setAppId(appId);
        pack.setUserId(userId);
        pack.setStatus(ImConnectStatusEnum.OFFLINE_STATUS.getCode());

        // 发送用户状态变更消息
        MqMessageProducer mqMessageProducer = (MqMessageProducer) ApplicationContextHelper.getBean(MqMessageProducer.class);
        mqMessageProducer.sendMessage(pack, messageHeader, UserEventCommand.USER_ONLINE_STATUS_CHANGE.getCommand());

        // 关闭通道
        nioSocketChannel.close();
    }

}